﻿using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows.Threading;

namespace ThePresenter.Net.NetCom
{
	class NetComServerFinder
	{
		public ServerInfoList AvailableServers
		{
			get { return this._Servers; }
		}

		public Boolean IsSearching
		{
			get { return this._IsSearching; }
		}

		private ServerInfoList _Servers;
		private Timer _DecayTimer;

		private UdpClient _Listener;
		private IPEndPoint _BroadcastEP;
		private Boolean _IsSearching;

		private Dispatcher _Dispatcher;

		public NetComServerFinder()
		{
			this._Servers = new ServerInfoList();
			this._IsSearching = false;
			this._Dispatcher = Dispatcher.CurrentDispatcher;
		}

		public void BeginSearch(int Port)
		{
			if (this.IsSearching)
			{
				this.EndSearch();
			}

			//try
			//{
				this._Servers.Clear();
				this._BroadcastEP = new System.Net.IPEndPoint(IPAddress.Any, Port);
				this._Listener = new UdpClient(this._BroadcastEP); // Just use broadcast endpoint, doesn't matter as long as IP is any
				this._Listener.EnableBroadcast = true;

				UdpClientReceiveState State = new UdpClientReceiveState();
				State.Owner = this;
				State.FinderThread = Thread.CurrentThread;

				this._Listener.BeginReceive(ReceiveCallback, State);
				this._IsSearching = true;

				this._DecayTimer = new Timer(new TimerCallback(this.Refresh), null, 0, (int)ThePresenter.Properties.Settings.Default.ServerBeaconTime.TotalMilliseconds / 2);
			//}
			//catch (System.Net.Sockets.SocketException SE)
			//{
			//    DebugClass.ShowMessage("NetComServerFinder.BeginSearch() : {0}", SE.Message);
			//}
		}

		public void EndSearch()
		{
			this._Listener.Close();
			this._DecayTimer.Dispose();
			this._IsSearching = false;
		}


		private delegate void RefreshDelegate(object State);

		private void Refresh(object State)
		{
			Debug.WriteLine("Refreshing...");

			lock (this._Servers)
			{
				for (int i = 0; i < this._Servers.Count; i++)
				{
					if (!this._Servers[i].IsValid)
					{
						if (!this._Dispatcher.Thread.Equals(Thread.CurrentThread))
							this._Dispatcher.Invoke(new ServerInfoList.RemoveDelegate(this._Servers.Remove), this._Servers[i]);
						else
							this._Servers.Remove(this._Servers[i]);

						Debug.WriteLine("Removed old serverinfo");
					}
				}
			}
		}

		public static void ReceiveCallback(IAsyncResult Result)
		{
			try
			{
				UdpClientReceiveState S = (UdpClientReceiveState)Result.AsyncState;

				lock (S.Owner)
				{ 
					IPEndPoint InfoSource = new IPEndPoint(IPAddress.Any, IPEndPoint.MinPort);

					Byte[] receiveBytes = S.Owner._Listener.EndReceive(Result, ref InfoSource);

					//if (!NetComTools.IsLocalIPAddress(InfoSource.Address)) // Filter own address, for future reference
					//{
						Debug.WriteLine(string.Format("Received {0} bytes from {1}", receiveBytes.Length, InfoSource.Address.ToString()));

						MemoryStream MS = new MemoryStream(receiveBytes);
						ServerInfo SI = (ServerInfo)NetComTools.BinaryFormatter.Deserialize(MS);

						lock (S.Owner._Servers)
						{
							if (!S.Owner._Servers.Contains(SI))
							{
								Debug.WriteLine(string.Format("Added {0}{{{1}}} to available servers", SI.Name, SI.Guid));
								SI.Name = SI.Name + " @ " + DateTime.Now.ToLongTimeString();
								SI.TTL = DateTime.Now.Add(ServerInfo.InfoTimeout);
								Dispatcher.FromThread(S.FinderThread).Invoke(new ThePresenter.Net.NetCom.ServerInfoList.AddDelegate(S.Owner._Servers.Add), SI);
							}
							else
							{
								S.Owner._Servers[S.Owner._Servers.IndexOf(SI)].TTL = DateTime.Now.Add(ServerInfo.InfoTimeout);
								Debug.WriteLine(string.Format("Added {0}{{{1}}} already in list", SI.Name, SI.Guid));
							}
						}
					//}

					S.Owner._Listener.BeginReceive(ReceiveCallback, S);
				}
			}
			catch (ObjectDisposedException){ } // Happens when connection is closed
		}

		public void Dispose()
		{
			this.EndSearch();
		}
	}


	class UdpClientReceiveState
	{
		/// <summary>
		/// Gets or sets the owner.
		/// </summary>
		/// <value>The owner.</value>
		public NetComServerFinder Owner { get; set; }

		/// <summary>
		/// Gets or sets the finder thread.
		/// </summary>
		/// <value>The finder thread.</value>
		public Thread FinderThread { get; set; }
	}
}
